package com.lazyframework.security.admin.configuration;

import com.lazyframework.commons.support.ResponseModels;
import com.lazyframework.security.admin.authentication.LoginLogService;
import com.lazyframework.security.admin.authentication.RestfullAuthenticationSuccessHandler;
import com.lazyframework.security.admin.authentication.JwtAuthenticationFailureHandler;
import com.lazyframework.security.admin.authentication.SecurityResponseStatus;
import com.lazyframework.security.admin.authentication.filter.JwtAuthenticationFilter;
import com.lazyframework.security.admin.authentication.filter.UsernamePasswordObtainFilter;
import com.lazyframework.security.admin.authentication.provider.JwtAuthenticationProvider;
import com.lazyframework.security.admin.properties.SecurityProperties;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.*;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * SpringSecurity自定义配置
 * Create by lazy in 2019.09.17
 */
@Configuration
public class CompositeSecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Autowired
    private LoginLogService loginLogService;

    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private UserDetailsService userDetailsService;

    /**
     * 通过调用父类构造函数禁用部分默认配置
     */
    public CompositeSecurityConfigurer() {
        super(false);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 资源访问控制
        http.authorizeRequests()
                .mvcMatchers(securityProperties.getPermitAll()).permitAll()
                .mvcMatchers(securityProperties.getAuthenticated()).authenticated()
                .anyRequest().access("@rbacService.hasPermission(request, authentication)");

        // 异常请求处理
        http.exceptionHandling()
                .accessDeniedHandler((request, response, e) -> ResponseModels.forbidden(response))
                .authenticationEntryPoint((request, response, e) -> ResponseModels.unauthorized(response));

        // 登录
        http.formLogin()
                .successHandler(authenticationSuccessHandler())
                .failureHandler((request, response, exception) -> {
                    if (exception instanceof UsernameNotFoundException) {
                        ResponseModels.print(response, SecurityResponseStatus.USERNAME_NOTFOUND);
                    } else if (exception instanceof BadCredentialsException) {
                        ResponseModels.print(response, SecurityResponseStatus.BAD_CREDENTIALS);
                    } else if (exception instanceof LockedException) {
                        ResponseModels.print(response, SecurityResponseStatus.ACCOUNT_LOCKED);
                    } else if (exception instanceof DisabledException) {
                        ResponseModels.print(response, SecurityResponseStatus.ACCOUNT_DISABLED);
                    } else if (exception instanceof AccountExpiredException) {
                        ResponseModels.print(response, SecurityResponseStatus.ACCOUNT_EXPIRED);
                    } else {
                        throw exception;
                    }
                })
                .loginProcessingUrl(securityProperties.getLoginProcessUrl()).permitAll();

        // 登出
        http.logout()
                .logoutRequestMatcher(new AntPathRequestMatcher(securityProperties.getLogoutUrl(), "POST"))
                .logoutSuccessHandler((request, response, authentication) ->
                        ResponseModels.ok(response, "您已退出登录")
                );

        // 只允许同源iframe
        http.headers().frameOptions().sameOrigin();

        // 处理跨域
        http.cors();

        // 禁用Security默认的CRSF策略
        http.csrf().disable();

        // 禁用Session, RESTFull API
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        // 用户名密码提取过滤器, FormLogin认证方式下, 默认的Content-Type: application-www-form-urlencoded
        http.addFilterBefore(usernamePasswordObtainFilter(), UsernamePasswordAuthenticationFilter.class);

        // JWT认证过滤器
        http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordObtainFilter.class);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        // JwtAuthenticationFilter
        JwtAuthenticationProvider jwtAuthenticationProvider = new JwtAuthenticationProvider();
        jwtAuthenticationProvider.setProperties(securityProperties.getJwt());
        jwtAuthenticationProvider.setAuthenticationType(securityProperties.getAuthenticationType());
        jwtAuthenticationProvider.setUserDetailsService(userDetailsService);
        auth.authenticationProvider(jwtAuthenticationProvider);

        // DaoAuthenticationFilter
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
        daoAuthenticationProvider.setUserDetailsService(this.userDetailsService);
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
        auth.authenticationProvider(daoAuthenticationProvider);
    }

    /**
     * 创建JWT认证过滤器
     *
     * @return
     * @throws Exception
     */
    private JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
        JwtAuthenticationFilter filter = new JwtAuthenticationFilter();
        AuthenticationManager authManager = super.authenticationManager();
        filter.setAuthenticationManager(authManager);
        filter.setAuthenticationFailureHandler(new DefaultJwtAuthenticationFailureHandler());
        filter.setExclusions(securityProperties.getPermitAll());
        filter.setAuthorizationParameter(securityProperties.getAuthenticationHeader());
        return filter;
    }

    private UsernamePasswordObtainFilter usernamePasswordObtainFilter() {
        return new UsernamePasswordObtainFilter(securityProperties.getLoginProcessUrl());
    }

    /**
     * 认证成功处理器
     * @return
     */
    private AuthenticationSuccessHandler authenticationSuccessHandler() {
        RestfullAuthenticationSuccessHandler handler = new RestfullAuthenticationSuccessHandler();
        handler.setLoginLogService(loginLogService);
        handler.setJwtProperties(securityProperties.getJwt());
        return handler;
    }

    static class DefaultJwtAuthenticationFailureHandler implements JwtAuthenticationFailureHandler {
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, JwtException exception)
                throws IOException {
            if (exception instanceof MalformedJwtException) {
                ResponseModels.print(response, SecurityResponseStatus.MALFORMED_JWT);
            } else if (exception instanceof UnsupportedJwtException) {
                ResponseModels.print(response, SecurityResponseStatus.UNSUPPORTED_JWT);
            } else if (exception instanceof SignatureException) {
                ResponseModels.print(response, SecurityResponseStatus.JWT_SIGNATURE_ERROR);
            } else if (exception instanceof ExpiredJwtException) {
                ResponseModels.print(response, SecurityResponseStatus.EXPIRED_JWT);
            } else {
                throw exception;
            }
        }
    }

}
